<!-- 表单 》 多选结构树 -->

<template>
  <el-form-item v-bind="formItem">
    <el-input v-show="false" :value="CORE.isEmpty(model) ? '' : '1'" />

    <!-- 标签 -->
    <template v-if="hasLabelSlot" #label>
      <slot name="label" :label="formItem.label">{{ formItem.label }}</slot>
    </template>

    <el-cascader
      v-if="dict.length"
      ref="tree"
      v-model="model"
      v-bind="commonProps"
      :popper-class="popperClass"
      :options="treeData"
      :props="props"
      :separator="and"
      :filterable="!noSearch"
      :show-all-levels="allName"
      :clearable="!noClear"
      @change="emitChange"
    >
      <template #default="{ data }">
        <div class="flexCC" :class="{ 'CoreWeb-option-only-leaf': onlyLeaf && data.children }">
          <slot :item="data">
            <span>{{ data.label }}</span>
            <span v-if="hasCount && data.children" class="CoreWeb-option-count">({{ data.children.length }})</span>
            <span class="CoreWeb-option-right">
              <span v-if="hasRight">{{ CORE.dict(nodeDict, data.value, rightIndex - 1) }}</span>
            </span>
          </slot>
        </div>
      </template>
    </el-cascader>

    <el-select
      v-else
      :value="undefined"
      v-bind="commonProps"
      :no-data-text="empty"
      :popper-class="`${className}-poper-empty`"
    />
  </el-form-item>
</template>

<script>
// ============================== 导入依赖 ============================== //

import CORE from '@/CORE'
import mixForm from '../mixForm'
import FormTree from '../FormTree'
import UTIL from '../FormTree/UTIL'

const name = 'FormTreeMulti'

// ============================== 导出组件 ============================== //

export default {
  /**
   * 名称定义 (实际使用名称)
   */
  name,

  /**
   * 混入列表 (配置选项合并)
   */
  mixins: [mixForm],

  /**
   * 属性注册 (抽取以便查阅)
   */
  props: CORE.WEB[name],

  /**
   * 状态数据 (函数返回对象)
   */
  data() {
    return {
      model: [], // 内部绑定值
      EMPTY: [], // 空值常量
    }
  },

  /**
   * 计算属性 (慎用箭头函数)
   */
  computed: {
    /**
     * 属性对象
     */
    props() {
      const { clickExpand, allId } = this
      return {
        expandTrigger: clickExpand ? 'click' : 'hover', // 鼠标悬停时展开
        emitPath: allId, // 选中时，是否返回路径 ID 列表
        multiple: true,
        checkStrictly: true, // 取消选中关联，从而可选任意一级
      }
    },

    /**
     * ID 列表的映射
     */
    idListMap: FormTree.computed.idListMap,

    /**
     * 名称列表的映射
     */
    nameListMap: FormTree.computed.nameListMap,

    /**
     * 节点字典
     */
    nodeDict: FormTree.computed.nodeDict,

    /**
     * 节点映射
     */
    nodeMap() {
      const { dict } = this
      return UTIL.getNodeMap({}, dict)
    },

    /**
     * 所有 ID 列表
     */
    allIdList() {
      return this.nodeDict.map(([id]) => id)
    },

    /**
     * 结构树数据
     */
    treeData() {
      const { dict, grayed, onlyLeaf, allIdList, nodeMap } = this
      if (!onlyLeaf) return UTIL.getTreeData([], dict, grayed)

      const notLeaf = allIdList.filter((id) => nodeMap[id][0].length)
      return UTIL.getTreeData([], dict, [...grayed, ...notLeaf])
    },

    /**
     * 弹层样式
     */
    popperClass() {
      const name = 'CoreWeb-popup-tree'
      const onlyLeafStr = this.onlyLeaf ? ` ${name}-onlyLeaf` : ''
      const popStr = this.popClass ? ` ${this.popClass}` : ''
      return `CoreWeb-popup-tree${onlyLeafStr}${popStr}`
    },
  },

  /**
   * 本地方法 (轻量重要在前)
   */
  methods: {
    /**
     * 是否不等 (可覆盖)
     */
    isDiff() {
      const { obj, prop, bindList, isMulti, model, allId, join, init } = this
      const v = obj[prop]

      if (allId) {
        // todo
        return JSON.stringify(v) !== JSON.stringify(model)
      }

      const values = join && CORE.isString(v) ? v ? v.split(',') : [] : v

      // 非数组
      if (!values || !CORE.isArray(values)) return true
      // 无值，但有默认值
      if (!values.length && !CORE.isEmpty(init)) return true
      // 有值，但不相等
      if (JSON.stringify(values) !== JSON.stringify(model)) return true

      // 相等，但个别选中项不存在
      const valuesDict = this.getValuesDict(values)
      if (valuesDict.some((item) => !item)) return true

      // 相等，且 bind 不是数组
      if (!isMulti) return false

      // 相等，判断 bind 的所有属性是否都相等
      if (join) {
        return bindList.some((key, i) => {
          if (!CORE.isString(obj[key.trim()])) return true
          return obj[key.trim()] !== valuesDict.map((item) => item[i]).join()
        })
      } else {
        return bindList.some((key, i) => {
          if (!CORE.isArray(obj[key.trim()])) return true
          return obj[key.trim()].join() !== valuesDict.map((item) => item[i]).join()
        })
      }
    },

    /**
     * 设置内部绑定值 (可覆盖)
     */
    setModel() {
      const { obj, prop, join, init, allId, EMPTY, allIdList } = this
      const v = obj[prop]
      if (allId) {
        this.model = v || EMPTY
        return
      }

      const values = join && CORE.isString(v) ? v ? v.split(',') : [] : v

      // 无值或非数组
      if (CORE.isEmpty(values) || !CORE.isArray(values)) this.model = init || EMPTY
      // 有值
      else this.model = values.filter((value) => allIdList.includes(value))
    },

    /**
     * 是否相等 (可覆盖)
     */
    equals(value) {
      const { obj, prop, bindList, isMulti, join, allId } = this
      const v = obj[prop]
      if (allId) {
        // todo
        return JSON.stringify(v) === JSON.stringify(value)
      }
      const values = join && CORE.isString(v) ? v ? v.split(',') : [] : v

      // 非数组
      if (!values || !CORE.isArray(values)) return false
      // 有值，但不相等
      if (values.join() !== value.join()) return false
      // 相等，且 bind 不是数组
      if (!isMulti) return true

      // 相等，判断 bind 的所有属性是否都相等
      const valuesDict = this.getValuesDict(values)
      if (join) {
        return bindList.every((key, i) => {
          if (!CORE.isString(obj[key.trim()])) return false
          return obj[key.trim()] === valuesDict.map((item) => item[i]).join()
        })
      } else {
        return bindList.every((key, i) => {
          if (!CORE.isArray(obj[key.trim()])) return false
          return obj[key.trim()].join() === valuesDict.map((item) => item[i]).join()
        })
      }
    },

    /**
     * 提交改变 (可覆盖)
     */
    emitChange() {
      const { obj, prop, bindList, isMulti, allId, join } = this

      // 获取新值
      const values = this.getValue()

      // 判断改变
      if (this.onlyShow || this.equals(values)) return

      // 更新绑定
      if (isMulti) {
        if (allId) {
          // todo
          this.$set(obj, prop, values)
          this.$emit('change', values)
        } else {
          const valuesDict = this.getValuesDict(values)
          if (join) bindList.forEach((key, i) => this.$set(obj, key, valuesDict.map(dict => dict[i]).join()))
          else bindList.forEach((key, i) => this.$set(obj, key, valuesDict.map(dict => dict[i])))
          this.$emit('change', values)
        }
      } else {
        this.$set(obj, prop, join ? values.join() : values)
        this.$emit('change', values)
      }

      // 提交之后
      this.afterEmit()
    },

    /**
     * 获取数值列表的字典
     */
    getValuesDict(values) {
      const { allName, and, nodeDict, nameListMap } = this

      const valuesDict = values.map((value) => nodeDict.find(([id]) => value === id))
      if (!allName) return valuesDict

      // 名称存全路径
      const valuesDictClone = CORE.clone(valuesDict)
      valuesDictClone.forEach((dict) => {
        if (dict) dict[1] = (nameListMap[dict[0]] || []).join(and)
      })
      return valuesDictClone
    },
  },
}
</script>
